/*
 * Copyright ©2021 su binglun(9085309@qq.com). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *---------------------------------------------------------------------
 *-                                                                   -
 *-           Website : http://phoenix.sunny360.cn/                   -
 *-                                                                   -
 *---------------------------------------------------------------------
 */
package cn.sunny360.core.common;

import cn.sunny360.messsage.MsgResult;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.impl.RouterImpl;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PhRouter extends RouterImpl {
    private static final int KB = 1024;
    private static final int MB = 1024 * KB;
    private static Logger logger = LoggerFactory.getLogger(PhRouter.class);

    private RouterInterceptorChain routerInterceptorChain;

    public static PhRouter phRouter(Vertx vertx, HttpHandlerMapping httpHandlerMapping) {
        return new PhRouter(vertx, httpHandlerMapping);
    }

    public static PhRouter phRouter(Vertx vertx) {
        return new PhRouter(vertx, null);
    }

    public PhRouter(Vertx vertx, HttpHandlerMapping httpHandlerMapping) {
        super(vertx);
        commonHandler();
        //cors handler
        corsAllowedHandler();
        if (httpHandlerMapping != null) {
            initHandler(httpHandlerMapping.getHandlerList());
        }
    }

    public RouterInterceptorChain getRouterInterceptorChain() {
        return routerInterceptorChain;
    }

    public void setRouterInterceptorChain(RouterInterceptorChain routerInterceptorChain) {
        this.routerInterceptorChain = routerInterceptorChain;
    }

    private void commonHandler() {
        // body handler

        this.route().handler(BodyHandler.create().setBodyLimit(10*MB));
        //404 If no route matches the path
        this.errorHandler(404, this::failureHandler);
        //405 If a route matches the path but don’t match the HTTP Method
        this.errorHandler(405, this::failureHandler);
        //406 If a route matches the path and the method but It can’t provide a response with a content type matching Accept header
        this.errorHandler(406, this::failureHandler);
        //415 If a route matches the path and the method but It can’t accept the Content-type
        this.errorHandler(415, this::failureHandler);
        //400 If a route matches the path and the method but It can’t accept an empty body
        this.errorHandler(400, this::failureHandler);
        //500 exception occurred
        this.errorHandler(500, this::failureHandler);
    }

    private void corsAllowedHandler() {
        Set<String> allowedHeaders = new HashSet<>();
        allowedHeaders.add("x-requested-with");
        allowedHeaders.add("Access-Control-Allow-Origin");
        allowedHeaders.add("origin");
        allowedHeaders.add("Content-Type");
        allowedHeaders.add("accept");
        allowedHeaders.add("X-PINGARUNER");

        Set<HttpMethod> allowedMethods = new HashSet<>();
        allowedMethods.add(HttpMethod.GET);
        allowedMethods.add(HttpMethod.POST);
        allowedMethods.add(HttpMethod.OPTIONS);
        /*
         * these methods aren't necessary for this sample,
         * but you may need them for your projects
         */
        allowedMethods.add(HttpMethod.DELETE);
        allowedMethods.add(HttpMethod.PATCH);
        allowedMethods.add(HttpMethod.PUT);

        this.route().handler(CorsHandler.create("*").allowedHeaders(allowedHeaders).allowedMethods(allowedMethods));
    }

    private void failureHandler(RoutingContext context) {
        int statusCode = context.statusCode();
        context.response()
                .setStatusCode(statusCode)
                .putHeader("content-type", "application/json;charset=utf-8")
                .end(PhResult.error(statusCode, context.failure()).toBuffer());
    }

    private void initHandler(List<HttpHandlerMapping.HandlerInfo> handlerInfoList) {
        handlerInfoList.stream().forEach(h->{
            Route route = null;
            if ("get".equalsIgnoreCase(h.getMethod())) {
                route = get(h.getPath());
            } else if ("post".equalsIgnoreCase(h.getMethod())) {
                route = post(h.getPath());
            } else if ("put".equalsIgnoreCase(h.getMethod())) {
                route = put(h.getPath());
            } else if ("delete".equalsIgnoreCase(h.getMethod())) {
                route = delete(h.getPath());
            } else if ("head".equalsIgnoreCase(h.getMethod())) {
                route = head(h.getPath());
            } else if ("options".equalsIgnoreCase(h.getMethod())) {
                route = options(h.getPath());
            } else if ("trace".equalsIgnoreCase(h.getMethod())) {
                route = trace(h.getPath());
            } else {
                route = route(h.getPath());
            }
            route.handler(context->{
                if (routerInterceptorChain != null) {
                    Promise<PhResult> promise = routerInterceptorChain.handle(context);
                    promise.future()
                            .onSuccess(result -> {
                                if (result.getCode() == 200) {
                                    h.getHandler().handle(context);
                                } else {
                                    HttpServerResponse response = context.request().response();
                                    MsgResult msgResult = toMsgResult(result);
                                    response.setStatusCode(200)
                                            .putHeader("content-type", "application/octet-stream;charset=utf-8")
                                            .end(Buffer.buffer(msgResult.toByteArray()));
                                }
                            })
                            .onFailure(e -> {
                                logger.error("拦截器执行发生异常", e);
                                HttpServerResponse response = context.request().response();
                                MsgResult msgResult = toMsgResult(PhResult.error(500, e));

                                response.setStatusCode(200)
                                        .putHeader("content-type", "application/octet-stream;charset=utf-8")
                                        .end(Buffer.buffer(msgResult.toByteArray()));
                            });
                } else {
                    h.getHandler().handle(context);
                }
            });
        });
    }

    private MsgResult toMsgResult(PhResult result) {
        MsgResult msgResult = MsgResult.newInstance()
                .setStatus(result.getCode())
                .setMsg(result.getMsg())
                .setData(result.getData());
        return msgResult;
    }
}
